package main

import (
	"crypto/rand"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

const maxUploadSize = 10000 * 1024 * 1024 // 2 mb
const uploadPath = "./"

const readBlock = 1024 * 4

const htmlUploader = `
	<html>
		<head>
			<title>Upload file</title>
		</head>
		<body>
			<form enctype="multipart/form-data" action="/upload" method="post">
				<input type="file" name="uploadFile" />
				<input type="submit" value="upload" />
			</form>
		</body>
	</html>
`

func main() {

	// 定义几个变量，用于接收命令行的参数值
	var dir string
	var port string
	var help bool
	// 接收命令行中输入 -dir 后面的参数值，其他同理
	flag.StringVar(&dir, "dir", uploadPath, "文件上传路径")
	flag.StringVar(&port, "port", "8080", "端口号，8080")
	flag.BoolVar(&help, "help", false, "帮助")
	flag.BoolVar(&help, "?", false, "帮助")
	// 解析命令行参数写入注册的flag里
	flag.Parse()
	// 输出结果
	fmt.Printf("dir：%v \n port：%v\n",
		dir, port)
	fmt.Println("-dir  指定下载文件的目录")
	fmt.Println("-port 指定服务监听端口")
	fmt.Println("-help 输出帮助文档")
	if help {
		return
	}

	http.HandleFunc("/upload", uploadNopFileHandler())
	http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write([]byte(htmlUploader))
	})

	fs := http.FileServer(http.Dir(dir))
	http.Handle("/files/", http.StripPrefix("/files", fs))

	log.Print("Server started on localhost:" + port + ", use /upload for uploading files and /files/{fileName} for downloading")
	log.Print("use folder " + dir + " as download files dir")
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

/*
*
仅读取内容，不做本地存储
*/
func uploadNopFileHandler() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" {
			//template.
			t, _ := template.ParseFiles("upload.gtpl")
			t.Execute(w, nil)
			return
		}
		r.Form.Get("upload")
		var unitKB int64
		// 当前的秒数 second
		startTime := time.Now().Unix()
		fmt.Printf("上传开始时间 %d \n", startTime)
		buf := make([]byte, readBlock)
		totalCnt := int64(0)
		cnt := int64(0)
		totalsecondsCost := int64(0)
		speedUnit := int64(5)
		avgspeed := int64(0)
		speed := int64(0)
		upLoadFinished := false
		counter := int64(0)
		maxSpeed := int64(0)
		unitKB = 1024
		for {
			n, err := r.Body.Read(buf)
			if err != nil || n == 0 {
				os.OpenFile("newFile", os.O_RDWR|os.O_CREATE, 0666)
				fmt.Println(err.Error())
				if "EOF" == err.Error() {
					fmt.Printf("文件上传完毕 %v %s \n", err, err.Error())
				} else {
					fmt.Printf("文件上传异常 %v %s \n", err, err.Error())
				}
				upLoadFinished = true
			}
			totalCnt = totalCnt + int64(n)
			totalsecondsCost = time.Now().Unix() - startTime
			cnt = cnt + int64(n)

			if totalsecondsCost > counter && totalsecondsCost > 0 && totalsecondsCost%int64(speedUnit) == 0 {
				avgspeed = totalCnt / (totalsecondsCost * unitKB)
				speed = cnt / (speedUnit * unitKB)
				if speed > maxSpeed {
					maxSpeed = speed
				}
				fmt.Println("======")
				fmt.Println("======")
				fmt.Println("======")
				content := fmt.Sprintf(" last %d second  \n\ttotal read  %d  KB \n\tavg speed is %d KB/s, \nlast %d  second \n\ttotal read  %d  KB \n\tspeed is %d KB/s "+
					"\nmax speed  %d KB \n ",
					totalsecondsCost, totalCnt/unitKB, avgspeed,
					speedUnit, totalCnt/unitKB, speed, maxSpeed)
				fmt.Printf(content)
				//w.Write([]byte(content))
				//
				cnt = 0
			}
			if upLoadFinished {
				fmt.Printf("file total size is %d  KB ,total time is %d second \n", totalCnt/unitKB, totalsecondsCost)
				break
			}
			counter = totalsecondsCost
		}

		w.Write([]byte(fmt.Sprintf("file total size is %d  KB \n === \n total time is %d second \n", totalCnt/unitKB, totalsecondsCost)))
	})
}

func uploadFileHandler() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
		w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Access-Control-Allow-Credentials", "true")
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}
		if r.Method == "GET" {
			//template.
			t, _ := template.ParseFiles("upload.gtpl")
			t.Execute(w, nil)
			return
		}
		startTime := time.Now()
		fmt.Printf("上传开始时间 %s \n", startTime)
		if err := r.ParseMultipartForm(maxUploadSize); err != nil {
			fmt.Printf("Could not parse multipart form: %v\n", err)
			renderError(w, "CANT_PARSE_FORM", http.StatusInternalServerError)
			return
		}
		finishTime := time.Now()
		fmt.Printf("上传结束时间[[1]] %s", finishTime)
		// parse and validate file and post parameters
		file, fileHeader, err := r.FormFile("uploadFile")
		if err != nil {
			renderError(w, "INVALID_FILE", http.StatusBadRequest)
			return
		}
		defer file.Close()
		// Get and print out file size
		fileSize := fileHeader.Size
		fmt.Printf("File size (bytes): %v\n", fileSize)
		// validate file size
		if fileSize > maxUploadSize {
			renderError(w, "FILE_TOO_BIG", http.StatusBadRequest)
			return
		}
		fileBytes, err := ioutil.ReadAll(file)
		finishTime = time.Now()
		fmt.Printf("上传结束时间[[2]] %s %s \n", finishTime, len(fileBytes))
		if err != nil {
			renderError(w, "INVALID_FILE", http.StatusBadRequest)
			return
		}
		fmt.Printf("上传结束时间[[3]] %s \n", finishTime)

		//check file type, detectcontenttype only needs the first 512 bytes
		detectedFileType := http.DetectContentType(fileBytes)
		switch detectedFileType {
		case "image/jpeg", "image/jpg":
		case "image/gif", "image/png":
		case "application/pdf":
			break
		default:
			renderError(w, "INVALID_FILE_TYPE", http.StatusBadRequest)
			return
		}
		fileName := randToken(12)
		fileEndings, err := mime.ExtensionsByType(detectedFileType)
		if err != nil {
			renderError(w, "CANT_READ_FILE_TYPE", http.StatusInternalServerError)
			return
		}
		newPath := filepath.Join(uploadPath, fileName+fileEndings[0])
		fmt.Printf("FileType: %s, File: %s\n", detectedFileType, newPath)

		// write file
		newFile, err := os.Create(newPath)
		if err != nil {
			renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
			return
		}
		defer newFile.Close() // idempotent, okay to call twice
		if _, err := newFile.Write(fileBytes); err != nil || newFile.Close() != nil {
			renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
			return
		}
		w.Write([]byte("SUCCESS"))
	})
}

func renderError(w http.ResponseWriter, message string, statusCode int) {
	w.WriteHeader(http.StatusBadRequest)
	w.Write([]byte(message))
}

func randToken(len int) string {
	b := make([]byte, len)
	rand.Read(b)
	return fmt.Sprintf("%x", b)
}
